package com.redisdemo.utlis;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
import java.util.List;

/**
 * <h3>redisDemo</h3>
 * <p>加密工具类,对称加密</p>
 *
 * @author gdw
 * @since 2024-06-26 16:55:02
 */
public class CryptoUtils {
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES";

    /**
     * 加密字符串类型
     * @param input
     * @param key
     * @return
     * @throws Exception
     */
    public static String encrypt(String input, String key) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
        byte[] encryptedBytes = cipher.doFinal(input.getBytes());
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    /**
     * 解密字符串类型
     * @param input
     * @param key
     * @return
     * @throws Exception
     */
    public static String decrypt(String input, String key) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
        byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(input));
        return new String(decryptedBytes);
    }
    /**
     * 生成（字符串类型的）密钥对象
     * @param key
     * @return
     */
    private static SecretKeySpec getSecretKey(String key) {
        return new SecretKeySpec(key.getBytes(), ALGORITHM);
    }

    /**
     * 加密对象类型
     * @param input
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptObject(Object input, String key) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKeyObj(key));

        // Serialize the object to byte array
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(input);
        oos.close();
        byte[] serializedObject = baos.toByteArray();

        // Encrypt the byte array
        byte[] encryptedBytes = cipher.doFinal(serializedObject);
        return Base64.getUrlEncoder().encodeToString(encryptedBytes);
    }

    /**
     * 解密对象类型
     * @param encryptedText
     * @param key
     * @return
     * @throws Exception
     */
    public static Object decryptObject(String encryptedText, String key) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, getSecretKeyObj(key));

        // Decode the base64 encoded string
        byte[] decodedBytes = Base64.getUrlDecoder().decode(encryptedText);

        // Decrypt the byte array
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);

        // Deserialize the byte array to object
        ByteArrayInputStream bais = new ByteArrayInputStream(decryptedBytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object decryptedObject = ois.readObject();
        ois.close();

        return decryptedObject;
    }

    /**
     * 生成（对象类型的）密钥对象
     * @param key
     * @return
     * @throws Exception
     */
    private static Key getSecretKeyObj(String key) throws Exception {
        byte[] keyBytes = key.getBytes(java.nio.charset.StandardCharsets.UTF_8);
        // Ensure the key is of the correct length
        byte[] keyBytesFixed = new byte[16]; // Use 16 bytes for AES-128, 24 bytes for AES-192, 32 bytes for AES-256
        System.arraycopy(keyBytes, 0, keyBytesFixed, 0, Math.min(keyBytes.length, keyBytesFixed.length));
        return new SecretKeySpec(keyBytesFixed, ALGORITHM);
    }

    /**
     * 加密List集合数据为字符串
     *
     * @param input 要加密的List<Object>
     * @param key   加密使用的密钥
     * @return 加密后的Base64编码字符串
     * @throws Exception 加密过程中的异常
     */
    public static String encryptList(List<Object> input, String key) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.ENCRYPT_MODE, getSecretKeyList(key));

        // Serialize the list to byte array
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(input);
        }
        byte[] serializedList = baos.toByteArray();

        // Encrypt the byte array
        byte[] encryptedBytes = cipher.doFinal(serializedList);
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    /**
     * 解密Base64编码字符串为List集合
     *
     * @param encryptedText 要解密的Base64编码字符串
     * @param key           解密使用的密钥
     * @return 解密后的List<Object>
     * @throws Exception 解密过程中的异常
     */
    public static List<Object> decryptList(String encryptedText, String key) throws Exception {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        cipher.init(Cipher.DECRYPT_MODE, getSecretKeyList(key));

        // Decode the base64 encoded string
        byte[] decodedBytes =  Base64.getDecoder().decode(encryptedText);

        // Decrypt the byte array
        byte[] decryptedBytes = cipher.doFinal(decodedBytes);

        // Deserialize the byte array to list
        try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(decryptedBytes))) {
            return (List<Object>) ois.readObject();
        }
    }

    /**
     * 生成List集合的密钥对象
     *
     * @param key 原始密钥字符串
     * @return 密钥对象
     * @throws Exception 密钥生成过程中的异常
     */
    private static SecretKey getSecretKeyList(String key) throws Exception {
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
        byte[] keyBytesFixed = new byte[16]; // Use 16 bytes for AES-128, 24 bytes for AES-192, 32 bytes for AES-256
        System.arraycopy(keyBytes, 0, keyBytesFixed, 0, Math.min(keyBytes.length, keyBytesFixed.length));
        return new SecretKeySpec(keyBytesFixed, ALGORITHM);
    }

}
